/*
 *  linux/boot/head.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  head.s contains the 32-bit startup code.
 *
 * NOTE# # #  Startup happens at absolute address 0x00000000, which is also where
 * the page directory will exist. The startup code will be overwritten by
 * the page directory.
 * 该模块的代码被放在system的开头，所以位于0地址处
 * 这个文件仍然是做软硬件相关的初始化配置
 */
.text
.code32
.globl idt,gdt,pg_dir,tmp_floppy_area
pg_dir:
.globl startup_32
startup_32:
	#  重新加载数据段寄存器，因为此时进入了保护模式，段寄存器的值含义发生了变化，需要改成择子
	#  使用选择子0x10，该选择子在setup.s中定义的GDT表中的第2项
	#  将所有的数据段寄存器全部设置成这一个
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	mov %ax,%gs
	#  设置内核的栈：不再使用原0x9FF00处，这些区域会被用作其它用途
	#  所以这里需要设置成自己的,stack_start在sched.s中定义, 即user_stack的末端地址
	#  lss会从中取值写入ss:esp两个寄存器中，完成栈的设置,4KB大小，一物理页
	#  相当于是进程0的栈空间
	lss stack_start,%esp

	#  配置idt表, 原来setup设置的是一个空的表项，所以这要重新配置
	call setup_idt

	#  在setup中设置的gdt表只是临时使用，用来辅助进入保护模式的
	#  并且该表也比较小，实际要求表项要比较大，所以这里重新配置了这样一个表
	call setup_gdt

	#  重新设置gdt表后，需要重新加载gdt选择子，以便这些段寄存器中的值可以获得新的配置项
	#  虽然都是数值0x10，但CPU实际上会根据这个数值从GDT中取表项的值更新到其内部，所以要这样重新设置
	#  CS可以考虑使用远跳转进行更新，但目前没有必要。因为两个新旧段之间的差异只在于界限不同
	#  原来的是8MB，现在是16MB
	#  cs不能通过mov指令进行设置
	movl $0x10,%eax		# reload all the segment registers
	mov %ax,%ds		# after changing gdt. CS was already
	mov %ax,%es		# reloaded in 'setup_gdt'
	mov %ax,%fs
	mov %ax,%gs
	#  再次重新加载ss:esp，仍然使用sched中的值， 使ss中的值再次生效
	lss stack_start,%esp

	#  比较0x0000-0x100000处的地址内容是否相同来判断1MB以上是否因A20地址线打开而可以访问
	#  通过向0地址写入一个值，然后与0x100000比较。如果两个值相同，可能是A20地址线未打开或者恰好相等
	#  为排除后者，自增eax，再写一个不同的数进行比较。如果不同就跳出，否则就继续重复判断，进入死循环
	xorl %eax,%eax		#  清0eax
1:	incl %eax			#  增加eax的值+1 # check that A20 really IS enabled
	movl %eax,0x000000	#  写入0x0 # loop forever if it isn't
	cmpl %eax,0x100000	#  和1MB的位置进行比较，如果相同，写入数值增加1，仍然往0写，再比较
	je 1b

/*
 * NOTE#  486 should set bit 16, to check for write-protect in supervisor
 * mode. Then it would be unnecessary with the "verify_area()"-calls.
 * 486 users probably want to set the NE (#5) bit also, so as to use
 * int 16 for math errors.
 */
	#  检测用于浮点运算运行的协处理器是否存在，486以前的机器没有这个，所以进行了处理
	movl %cr0,%eax		# check math chip
	andl $0x80000011,%eax	# Save PG,PE,ET
/* "orl $0x10020,%eax" here for 486 might be good */
	orl $2,%eax		# set MP
	movl %eax,%cr0

	#  协处理器检查
	call check_x87


	jmp after_page_tables

/*
 * We depend on ET to be correct. This checks for 287/387.
 * 检查协处理器是否存在，不需要深入了解
 */
check_x87:
	fninit
	fstsw %ax
	cmpb $0,%al
	je 1f			/* no coprocessor: have to set bits */
	movl %cr0,%eax
	xorl $6,%eax		/* reset MP, set EM */
	movl %eax,%cr0
	ret

.align 4
	#  给协处理器使用的代码，不需要关心
1:	.byte 0xDB,0xE4		/* fsetpm for 287, ignored by 387 */
	ret

/*
 *  setup_idt
 *
 *  sets up a idt with 256 entries pointing to
 *  ignore_int, interrupt gates. It then loads
 *  idt. Everything that wants to install itself
 *  in the idt-table may do so themselves. Interrupts
 *  are enabled elsewhere, when we can be relatively
 *  sure everything is ok. This routine will be over-
 *  written by the page tables.
 */
#  配置idt代码，相当于以下代码
#  uint32_t * ptr = idt; 
#  for (int i = 0; i < 256; i++) {
#     *ptr++ = 0x00080000 | ignore_int;
#     *ptr++ = 0x8E00;
# }
#  然后：lidt(&idt_descr)，加载新的idt表
#  即将idt表全部设置为指向一个空的处理函数ignore_int，见下边
#  这样以后的中断，如果没设置中断处理函数，相应中断发生时将自动调用ignore_int
setup_idt:
	#  配置好要写的两个4字节的值
	lea ignore_int,%edx		#  将ignore_int的有效地址加载到edx
	movl $0x00080000,%eax	#  段选择子为8，设到高16位中
	movw %dx,%ax		/* 将低16位设置成偏移量*/
	movw $0x8E00,%dx	/* interrupt gate - dpl=0, present，设置idt的属性*/

	#  重复设置256个表项
	lea idt,%edi		#  取idt的地址
	mov $256,%ecx		#  重复256次
rp_sidt:
	movl %eax,(%edi)		#  写idt表项低4个字节
	movl %edx,4(%edi)		#  再次下4个字节，属性
	addl $8,%edi			#  到下一个表贡
	dec %ecx				#  计数减1
	jne rp_sidt

	lidt idt_descr				#  加载新的idt描述符
	ret

/*
 *  setup_gdt
 *
 *  This routines sets up a new gdt and loads it.
 *  Only two entries are currently built, the same
 *  ones that were built in init.s. The routine
 *  is VERY complicated at two whole lines, so this
 *  rather long comment is certainly needed :-).
 *  This routine will beoverwritten by the page tables.
 */
setup_gdt:
	lgdt gdt_descr
	ret

/*
 * I put the kernel page tables right after the page directory,
 * using 4 of them to span 16 Mb of physical memory. People with
 * more than 16MB will have to expand this.
 */
#  1个页目录表+4个页表，可以映射前16MB的内容（每个页表可映射4MB）
#  这16MB正好对应于物理内存16MB
.org 0x1000
pg0:

.org 0x2000
pg1:

.org 0x3000
pg2:

.org 0x4000
pg3:

.org 0x5000
/*
 * tmp_floppy_area is used by the floppy-driver when DMA cannot
 * reach to a buffer-block. It needs to be aligned, so that it isn't
 * on a 64kB border.
 */
 #  给软盘使用的缓存区，共1KB
tmp_floppy_area:
	.fill 1024,1,0

after_page_tables:
	#  后面的代码似乎可以用下面的代码替代，功能是看起来差不多。。。。
.if 0
	call setup_paging
	pushl $0		# These are the parameters to main :-)
	pushl $0
	pushl $0
	call main		#  进入init/main.c
.else
	#  main函数完整形式为：int main(int argc, char ** argv, char ** env);
	#  所以这里是将三个参数全部设置为0。不过在init.c中好像并没有这样声明这个函数的使用
	pushl $0	# $0		# These are the parameters to main :-)
	pushl $1	# $0
	pushl $2	# $0
	pushl $L6		# main函数的返回地址，其不应当返回，但还是填了一个
	pushl $main		#  进入init/main.c
	#  这里并没有使用call指令去调用，而是压入栈中，将main作为当前函数的返回地址
	#  然后在setup_paging中执行ret返回时切换过去.这样好像没什么必要。。。。

	#  分页设置 
	jmp setup_paging
.endif

	#  死循环，防止main返回，但是这种情况是一般是不会发生的
L6:
	jmp L6			# main should never return here, but
				# just in case, we know what happens.

/* This is the default interrupt "handler" :-) */
#  缺省中断处理函数，简单的打印了一个"Unknown interrupt\n\r"信息
int_msg:
	.asciz "Unknown interrupt\n\r"
.align 2
ignore_int:
	#  在调用int_msg前和中，以下寄存器会更改，所以下面这些信息需要保存起来
	#  根据abi386 SYSTEM V APPLICATION BINARY INTERFACE文档：ebp, ebx, edi, esi, esp由printk保存和恢复
	#  但是eax, ecx, edx, ds, es,fs这些需要调用者自己保存，被调函数可能修改
	#  所以下载进行了各种保存
	pushl %eax
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	push %fs

	#  进入内核，使用内核相应的数据段寄存器选择子。之前可能是跑的用户进程，数据段特权级低
	#  因为在进入中断前，可能是应用级别的程序运行，此时用的选子择是应用的，不是内核的
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs

	#  调用printk函数
	pushl $int_msg		#  压入要显示的信息，然后显示
	call printk
	popl %eax			#  恢复pushl $int_msg对应的地址，方便下面进行上下文恢复

	#  从内核返回，恢复相应的寄存器
	pop %fs
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	iret


/*
 * Setup_paging
 *
 * This routine sets up paging by setting the page bit
 * in cr0. The page tables are set up, identity-mapping
 * the first 16MB. The pager assumes that no illegal
 * addresses are produced (ie >4Mb on a 4Mb machine).
 *
 * NOTE#  Although all physical memory should be identity
 * mapped by this routine, only the kernel page functions
 * use the >1Mb addresses directly. All "normal" functions
 * use just the lower 1Mb, or the local data space, which
 * will be mapped to some other place - mm keeps track of
 * that.
 *
 * For those with more memory than 16 Mb - tough luck. I've
 * not got it, why should you :-) The source is here. Change
 * it. (Seriously - it shouldn't be too difficult. Mostly
 * change some constants etc. I left it at 16Mb, as my machine
 * even cannot be extended past that (ok, but it was cheap :-)
 * I've tried to show which constants to change by having
 * some kind of marker at them (search for "16Mb"), but I
 * won't guarantee that's all :-( )
 * 设置内核页表
 * 建立16MB以下内存的恒等映射，即物理地址和虚拟地址相同
 * 当时的机器内存容量比较小，linus的机器不超过16MB，所以没有大内存的支持
 */
.align 2
setup_paging:
	#  页目录项的设置，共5个表，每个表为1024个表项，将所有的表项全部清0
	#  其中页目录表是从0地址开始，即将head.s的开始的4KB空间给覆盖了，即便前面有代码
	#  之所以这么做，还是为了节省内存，以前的内存很少
	movl $1024*5,%ecx		/* 5 pages - pg_dir+4 page tables */
	xorl %eax,%eax			
	xorl %edi,%edi			/* pg_dir is at 0x000 */
	cld;rep;stosl

	#  设置页目录表的前4项，指向页表
	#  只映射了前16MB，所以只需要设置前4个表项, 存在，用户模式，可读写
	#  在这里可以查看qemu的页表来分析
	movl $pg0+7,pg_dir		/* set present bit/user r/w */
	movl $pg1+7,pg_dir+4		/*  --------- " " --------- */
	movl $pg2+7,pg_dir+8		/*  --------- " " --------- */
	movl $pg3+7,pg_dir+12		/*  --------- " " --------- */

	#  从页表的最后一项，偏移量为。。4092字节数（总大小为4096）反向写
	#  将其从最后16MB的最后一页物理地址依次递减到，完成0~16MB范围内的映射
	#  总共写完4个页表，最后一项的地址为16M-4KB=0x1000000-0x1000=0xFFF000
	movl $pg3+4092,%edi		#  最后一项的位置，放在edi中
	movl $0xfff007,%eax		/*  最后一项的物理地址(16M-4KB)0xFFF000+属性值16Mb - 4096 + 7 (r/w user,p) */
	std
1:	stosl			/* fill pages backwards - more efficient :-) */
	subl $0x1000,%eax		#  物理页大小为4KB，每写完1页后，地址减去1个物理页大小
	jge 1b

	#  设置CR3，指向0地址处，即使用新的页表
	xorl %eax,%eax		/* pg_dir is at 0x0000，清0，也可以使用mov $0, %eax */
	movl %eax,%cr3		/* cr3 - page directory start */

	#  将PG置1，启用分页机制
	movl %cr0,%eax
	orl $0x80000000,%eax
	movl %eax,%cr0		/* set paging (PG) bit */
	ret			/* this also flushes prefetch-queue */

#  IDT表，256个表项
.align 8
.word 0
idt_descr:
	.word 256*8-1		# idt contains 256 entries
	.long idt

#  GDT表，共256个表项
.align 8
.word 0
gdt_descr:
	.word 256*8-1		# so does gdt (not that that's any
	.long gdt		# magic number, but it works for me :^)

#  IDT表全部为空，在初始化时会设置,256个表项，每个表项8字节
	.align 8
idt:	.fill 256,8,0		# idt is uninitialized

#  GDT表项，256个表项，每个表项为空
#  以前内存不大，所以这里只设置16MB
gdt:	
	.quad 0x0000000000000000	/* 第0个为空，NULL descriptor */
	.quad 0x00c09a0000000fff	/* 16Mb，属性和setup中差不多 */
	.quad 0x00c0920000000fff	/* 16Mb，属性和setup中差不多 */
	.quad 0x0000000000000000	/* TEMPORARY - don't use */
	.fill 252,8,0			/* space for LDT's and TSS's etc */
